"""
Class in this file declares macros
"""
import ast
from typing import List, Union, TYPE_CHECKING
from llvmlite import ir

if TYPE_CHECKING:
    from ..ast_parser import FunctionVisitor


class PMBuiltinMacro:
    def __init__(self, name: str):
        self.name = name

    def __call__(self, visitor: "FunctionVisitor", builder: ir.IRBuilder, args: List[Union[ast.Name, ast.Constant]],
                 name=''):
        raise NotImplementedError


class PMGetObjectAttrByOffset(PMBuiltinMacro):

    def __init__(self):
        super().__init__('getattr_by_offset')

    def __call__(self, visitor, builder: ir.IRBuilder, args: List[Union[ast.Name, ast.Constant]], name=''):
        from ..types import PMObjectType, integer
        assert len(args) == 2
        assert isinstance(args[0], ast.Name)
        obj = visitor.eval_node_value(args[0])
        obj_type = visitor.get_var_type(args[0].id)

        offset = args[1]
        assert isinstance(offset, ast.Constant)
        const = offset.value
        assert isinstance(obj_type, PMObjectType)
        attr_value, attr_type = obj_type.getattr_by_offset(builder, obj, offset.value, name)
        return attr_value, attr_type


class PMObjectFromBuffer(PMBuiltinMacro):

    def __init__(self):
        super().__init__('from_buffer_object')

    def __call__(self, visitor: "FunctionVisitor", builder: ir.IRBuilder, args: List[Union[ast.Name, ast.Constant]],
                 name=''):
        from ..types import PMObjectType
        assert len(args) == 2
        ptr_addr = visitor.eval_node_value(args[0])
        arg1 = args[1]
        assert isinstance(arg1, ast.Name)
        cls_id = arg1.id
        class_: PMObjectType = visitor.custom_types[cls_id]
        created = class_.from_buffer(builder, ptr_addr, name)
        return created, class_


class PMGenericObjectFromBuffer(PMBuiltinMacro):

    def __init__(self):
        super().__init__('from_buffer_generic_object')

    def __call__(self, visitor: "FunctionVisitor", builder: ir.IRBuilder, args: List[Union[ast.Name, ast.Constant]],
                 name=''):
        from ..types import PMObjectType
        assert len(args) == 3
        ptr_addr = visitor.eval_node_value(args[0])
        arg1 = args[1]
        arg2 = args[2]
        assert isinstance(arg1, ast.Name)
        assert isinstance(arg2, ast.Name)
        cls_id = arg1.id
        generic_type = arg2.id
        class_: PMObjectType = visitor.custom_types[cls_id + f"<T {generic_type}>"]
        created = class_.from_buffer(builder, ptr_addr, name)
        return created, class_


class PMArrayFromBuffer(PMBuiltinMacro):

    def __init__(self):
        super().__init__('from_buffer_array')

    def __call__(self, visitor: "FunctionVisitor", builder: ir.IRBuilder, args: List[Union[ast.Name, ast.Constant]],
                 name=''):
        from ..types import PMObjectType, PMArrayType
        assert len(args) == 2
        ptr_addr = visitor.eval_node_value(args[0])
        arg1 = args[1]
        assert isinstance(arg1, ast.Name)
        cls_id = arg1.id
        class_: PMObjectType = visitor.custom_types[cls_id]
        created = PMArrayType(class_).from_buffer_with_auto(builder, ptr_addr, name)
        return created, class_


class BuiltinMacros:
    def __init__(self):
        self.funcs = {}
        self.add_macro(PMGetObjectAttrByOffset())
        self.add_macro(PMObjectFromBuffer())
        self.add_macro(PMArrayFromBuffer())
        self.add_macro(PMGenericObjectFromBuffer())

    def has_macro(self, func_name):
        return func_name in self.funcs

    def add_macro(self, f: PMBuiltinMacro):
        assert isinstance(f, PMBuiltinMacro)
        self.funcs[f.name] = f

    def get_macro(self, func_name: str) -> PMBuiltinMacro:
        return self.funcs[func_name]


builtin_macros = BuiltinMacros()
